﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using System.Web.Security;
using BGCMS.Models;

namespace BGCMS.Controllers
{
    /// <summary>
    /// SpecialistController contains all logic connected to registering and editing Specialists
    /// and displaying information about them. Different authorization is implemented for
    /// diffrent methods, however no methods are accessible to non-authorized users.
    /// </summary>
    public class SpecialistController : Controller
    {
        #region Variables

        BGRepository rep = null;

        #endregion Variables

        #region Constructor

        public SpecialistController()
        {
            rep = new BGRepository();
        }

        #endregion Constructor

        #region Private member functions

        // A helper function that recieves a collection of Specialists and for retrieves
        // additional information for each one (i.e. Email and UserName).
        // Returns a collection of SpecialistViewModel objects
        private List<SpecialistViewModel> GetSpecialistsViewModel(IEnumerable<Specialist> specialists)
        {
            List<SpecialistViewModel> list = new List<SpecialistViewModel>();
            foreach (var u in specialists)
            {
                MembershipUser user = Membership.GetUser(u.UserID);
                SpecialistViewModel details = new SpecialistViewModel { Specialist = u, Email = user.Email, UserName = user.UserName };
                list.Add(details);
            }
            return list;
        }

        #endregion Private member functions

        #region Display methods

        /// <summary>
        /// Displays the main welcome page for Specialists, which provides a list of actions,
        /// and a list of unanswered questions.
        /// </summary>
        /// <returns>Get: /Specialist</returns>
        [Authorize]
        public ActionResult Index()
        {
            // If the current user is not already a specialist redirect to application to become one
            if (!Roles.IsUserInRole(User.Identity.Name, "Specialists"))
            {
                return RedirectToAction("CreateSpecialist");
            }

            var questions = from q in rep.GetAllQuestions()
                            where q.IsAnswered == false
                            select q;

            // Get the current specialist:
            MembershipUser user = Membership.GetUser(User.Identity.Name);
            var specialist = (from s in rep.GetAllSpecialist()
                              where s.UserID == (Guid)user.ProviderUserKey
                              select s).SingleOrDefault();

            SpecialistIndexViewModel model = new SpecialistIndexViewModel { Questions = questions, Specialist = specialist };

            return View(model);
        }

        /// <summary>
        /// Displays a list of approved specialists.
        /// Only accessible to other approved specialists.
        /// </summary>
        /// <returns>Get: /Specialist/Specialists</returns>
        [Authorize(Roles = "Specialists")]
        public ActionResult Specialists()
        {
            var specialists = (from s in rep.GetAllSpecialist()
                               orderby s.Name ascending
                               where s.IsApproved == true
                               select s);

            // Retrieve further information about the specialists
            // with a collection of SpecialistViewModel objects
            var model = GetSpecialistsViewModel(specialists);
            return View(model);
        }

        /// <summary>
        /// Displays detailed information about a particular Specialist
        /// Only accessible to other approved specialists.
        /// </summary>
        /// <param name="id">The ID of the Specialist (same as his user ID)</param>
        /// <returns>Get: /Specialist/SpecialistDetails/[id]</returns>
        [Authorize(Roles = "Specialists")]
        public ActionResult SpecialistDetails(Guid? id)
        {
            if (id.HasValue)
            {
                var specialist = (from s in rep.GetAllSpecialist()
                                  where s.UserID == id.Value
                                  select s).SingleOrDefault();

                if (specialist == null)
                {
                    return View("NotFound");
                }

                // Get additional information about the Specialist (Email and UserName)
                MembershipUser user = Membership.GetUser(specialist.UserID);
                SpecialistViewModel model = new SpecialistViewModel { Specialist = specialist, Email = user.Email, UserName = user.UserName };

                return View(model);
            }
            return View("NotFound");
        }

        /// <summary>
        /// Displays a list of approved specialists with an option to disapprove them
        /// Only accessible to Administrator users
        /// </summary>
        /// <returns>Get: /Specialist/ApprovedSpecialists</returns>
        [Authorize(Roles = "Administrators")]
        public ActionResult ApprovedSpecialists()
        {
            var specialists = (from s in rep.GetAllSpecialist()
                               orderby s.Name ascending
                               where s.IsApproved == true
                               select s);

            // Retrieve further information about the specialists
            // with a collection of SpecialistViewModel objects
            var model = GetSpecialistsViewModel(specialists);

            return View(model);
        }

        /// <summary>
        /// Displays a list of specialists that have not been approved with an option to approve them.
        /// Only accessible to Administrator users.
        /// </summary>
        /// <returns>Get: /Specialist/PendingSpecialists</returns>
        [Authorize(Roles = "Administrators")]
        public ActionResult PendingSpecialists()
        {
            var specialists = (from s in rep.GetAllSpecialist()
                               orderby s.Name ascending
                               where s.IsApproved == false
                               select s);

            // Retrieve further information about the specialists
            // with a collection of SpecialistViewModel objects
            var model = GetSpecialistsViewModel(specialists);

            return View(model);
        }

        #endregion Display methods

        #region Create/Edit methods

        /// <summary>
        /// Displays an application form for a user to register himself as a specialist.
        /// Accessible to all authorized users.
        /// </summary>
        /// <returns>Get: /Specialist/CreateSpecialist</returns>
        [Authorize]
        public ActionResult CreateSpecialist()
        {
            // Check if user has aleady applied to be a specialist:
            MembershipUser user = Membership.GetUser(User.Identity.Name);
            var specialist = (from s in rep.GetAllSpecialist()
                              where s.UserID == (Guid)user.ProviderUserKey
                              select s).SingleOrDefault();

            if (specialist != null)
            {
                // Only way for an approved specialist to get here is to type in the URL
                if (specialist.IsApproved == true)
                    return View("NotFound");
                else
                    return View("WaitingSpecialist");   // For unapproved specialists
            }
            // At this point it has been verified that the user has not previously applied
            return View(new Specialist());
        }

        /// <summary>
        /// Registers information from the application form for a Specialist
        /// </summary>
        /// <param name="collection">The form fields filled out by the user</param>
        /// <returns>Get: /Specialist/SpecialistSuccessCreate</returns>
        [Authorize]
        [HttpPost]
        public ActionResult CreateSpecialist(FormCollection collection)
        {
            Specialist s = new Specialist();

            MembershipUser user = Membership.GetUser(User.Identity.Name);
            if (user == null)
            {
                return View("UserNotFound");
            }
            s.UserID = (Guid)user.ProviderUserKey;

            try
            {
                UpdateModel(s);
                rep.UpdateSpecialist(s);
                rep.SaveChanges();

                return View("SpecialistSuccessCreate");
            }
            catch
            {   // On failure to save to database the form is redisplayed
                foreach (var issue in s.SpecialistGetRuleViolations())
                {
                    ModelState.AddModelError(issue.SpecialistPropertyName, issue.SpecialistErrorMessage);
                }

                return View(s);
            }
        }

        /// <summary>
        /// Displays a form to edit an existing Specialist
        /// Accessible to all Specialist user, however if a Specialist that is is not an administrator
        /// tries to edit information about someone other than himself, he is redirected to a "NotFound" view
        /// </summary>
        /// <param name="id">The ID of the Specialist (same as his user ID)</param>
        /// <returns>Get: /Specialist/EditSpecialist/[ID]</returns>
        [Authorize(Roles = "Specialists")]
        public ActionResult EditSpecialist(Guid? id)
        {
            if (id.HasValue)
            {
                var model = (from s in rep.GetAllSpecialist()
                             where s.UserID == id.Value
                             select s).SingleOrDefault();

                MembershipUser user = Membership.GetUser(User.Identity.Name);

                // Verify that the Specialist exists and preventing that a the current user that is
                // not an Administrator can edit information about another Specialist.
                if (model == null || ((Guid)user.ProviderUserKey != id.Value && !Roles.IsUserInRole(user.UserName, "Administrators")))
                {
                    return View("NotFound");
                }

                return View(model);
            }

            else
                return View("NotFound");
        }

        /// <summary>
        /// Registers changes made to an existing specialists.
        /// </summary>
        /// <param name="id">The ID of the Specialist (same as his user ID)</param>
        /// <param name="formdata">The form fields filled out by the user</param>
        /// <returns>If the user is an Administrator: Get: /Specialist/ApprovedSpecialists
        /// If the user is a Specialist, but not Administrator: Get: /Specialist/ (index method)</returns>
        [Authorize(Roles = "Specialists")]
        [HttpPost]
        public ActionResult EditSpecialist(Guid? id, FormCollection formdata)
        {
            var result = (from s in rep.GetAllSpecialist()
                          where s.UserID == id.Value
                          select s).SingleOrDefault();

            try
            {
                UpdateModel(result);
                rep.SaveChanges();
            }
            catch
            {   // On failure to save to database the form is redisplayed
                foreach (var issue in result.SpecialistGetRuleViolations())
                {
                    ModelState.AddModelError(issue.SpecialistPropertyName, issue.SpecialistErrorMessage);
                }
                return View(result);
            }

            // An administrator is redirected to the appropriate administrator page
            if (Roles.IsUserInRole(User.Identity.Name, "Administrators"))
                return RedirectToAction("ApprovedSpecialists");
            return RedirectToAction("Index");
        }

        #endregion Create/Edit methods

        #region Approve/Deny methods

        /// <summary>
        /// Adds a Specialist to the Specialist role and sends an email notification to the Specialist.
        /// Only accessible to Administrator users
        /// </summary>
        /// <param name="id">The ID of the Specialist (same as his user ID)</param>
        /// <returns>Get: /Specialist/PendingSpecialists</returns>
        [Authorize(Roles = "Administrators")]
        public ActionResult ApproveSpecialist(Guid? id)
        {
            if (id.HasValue)
            {
                Specialist s = (from i in rep.GetAllSpecialist()
                                where i.UserID == id.Value
                                select i).SingleOrDefault();

                if (s == null)
                {
                    return View("Error");
                }

                // Add the user to the Specialists role:
                MembershipUser user = Membership.GetUser(id.Value);
                Roles.AddUserToRole(user.UserName, "Specialists");
                Membership.UpdateUser(user);

                // Update the information in the Specialist table:
                s.IsApproved = true;
                UpdateModel(s);
                rep.SaveChanges();

                // Send email to confirm application approval:
                string recipient = user.Email;
                StringBuilder body = new StringBuilder("<p>Velkomin ");
                body.Append(s.Name);
                body.Append(".</p><p>Umsókn þín sem sérfræðingur á síðunni <a href='www.brjostagjof.is'>brjóstagjöf.is</a> hefur verið samþykkt.</p>");
                body.Append("<p>Bestu kveðjur, með von um virka þáttöku</p>");
                body.Append("<p>Umsjónarmenn</p>");

                Email.SendEmail(recipient, "Umsókn samþykkt - brjóstagjöf.is", body.ToString());

                return RedirectToAction("PendingSpecialists");
            }
            return View("NotFound");
        }

        /// <summary>
        /// Removes a Specialist from the Specialists role.
        /// Only accessible to Administrator users
        /// </summary>
        /// <param name="id">The ID of the Specialist (the same as his user ID)</param>
        /// <returns>Get: /Specialist/ApprovedSpecialists</returns>
        [Authorize(Roles = "Administrators")]
        public ActionResult DenySpecialist(Guid? id)
        {
            if (id.HasValue)
            {
                Specialist s = (from i in rep.GetAllSpecialist()
                                where i.UserID == id.Value
                                select i).SingleOrDefault();

                if (s == null)
                {
                    return View("Error");
                }

                // Check to see if user to be denied is also an administrator
                MembershipUser user = Membership.GetUser(id.Value);
                if (Roles.IsUserInRole(user.UserName, "Administrators"))
                {
                    return View("DenySpecialistError");
                }

                // Remove user from Specalist role
                Roles.RemoveUserFromRole(user.UserName, "Specialists");
                Membership.UpdateUser(user);

                // Update specialist information in the Specialists table
                s.IsApproved = false;
                UpdateModel(s);
                rep.SaveChanges();
                return RedirectToAction("ApprovedSpecialists");
            }
            return View("NotFound");
        }

        #endregion Approve/Deny methods
    }
}